package com.xlh.virtualization.websocket;

import com.alibaba.fastjson.JSONObject;
import com.google.common.collect.HashBasedTable;
import com.google.common.collect.Lists;
import com.google.common.collect.Table;
import com.xlh.enums.user.RoleEnum;
import com.xlh.util.SpringContextUtil;
import com.xlh.virtualization.job.delayed.StopContainerListener;
import com.xlh.virtualization.job.delayed.StopContainerTask;
import com.xlh.virtualization.service.ExperimentStatisticService;
import com.xlh.virtualization.websocket.message.FlavorUpdateMessage;
import com.xlh.websocket.GetHttpSessionConfigurator;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections.CollectionUtils;
import org.springframework.stereotype.Component;

import javax.websocket.*;
import javax.websocket.server.PathParam;
import javax.websocket.server.ServerEndpoint;
import java.io.IOException;
import java.util.*;

import static com.xlh.virtualization.constant.ContextTypeEnum.TRAINING;

/**
 * 环境实训实验WebSocket，用于记录实验时长和停止容器
 *
 * @author cheer
 */
@ServerEndpoint(value = "/ws/experiment/training/{trainingId}", configurator = GetHttpSessionConfigurator.class)
@Component
@Slf4j
public class TrainingExperimentWebsocket {

    private static final ExperimentStatisticService experimentStatisticService = SpringContextUtil.getBean(ExperimentStatisticService.class);

    /**
     * 存储连接 userId , trainingId, websocketList
     */
    private static Table<Long, Long, List<TrainingExperimentWebsocket>> connectionMap = HashBasedTable.create();

    /**
     * 存储计时 userId, trainingId, date
     */
    private static Table<Long, Long, Date> startTimeMap = HashBasedTable.create();

    private Session session;

    /**
     * 用户Id
     */
    private Long userId;

    /**
     * 实训Id
     */
    private Long trainingId;

    /**
     * 是否为学生
     */
    private Boolean isStudent;

    @OnOpen
    public void onOpen(Session session, @PathParam("trainingId") Long trainingId, EndpointConfig config) {
        this.session = session;
        this.userId = (Long) config.getUserProperties().get("userId");
        this.trainingId = trainingId;
        this.isStudent = Objects.equals(RoleEnum.STUDENT.getRoleCode(), config.getUserProperties().get("roleId"));

        synchronized (TrainingExperimentWebsocket.class) {
            if (connectionMap.contains(userId, trainingId)) {
                List<TrainingExperimentWebsocket> websocketList = connectionMap.get(userId, trainingId);
                websocketList.add(this);
            } else {
                connectionMap.put(userId, trainingId, Lists.newArrayList(this));
            }

            if (isStudent && !startTimeMap.contains(userId, trainingId)) {
                startTimeMap.put(userId, trainingId, new Date());
            }
        }
    }

    @OnClose
    public void onClose() {
        synchronized (TrainingExperimentWebsocket.class) {
            List<TrainingExperimentWebsocket> websocketList = connectionMap.get(userId, trainingId);
            if (websocketList != null) {
                websocketList.remove(this);
            }
            if (CollectionUtils.isEmpty(websocketList)) {
                // 停止容器
                StopContainerListener.putTask(new StopContainerTask(userId, TRAINING.getCode(), trainingId));

                // 计时统计
                if (isStudent) {
                    Date startTime = startTimeMap.remove(userId, trainingId);
                    Date endTime = new Date();
                    experimentStatisticService.saveExperimentDuration(userId, TRAINING.getCode(), trainingId, startTime, endTime);
                }

                connectionMap.remove(userId, trainingId);
            }
        }
    }

    @OnMessage
    public void onMessage(String jsonStr, Session session) {
        // 暂时不需要实现该方法
    }

    @OnError
    public void onError(Session session, Throwable error) {
        // 暂时不需要实现该方法
    }

    public static void notifyUpdateFlavor(Long trainingId) {
        Map<Long, List<TrainingExperimentWebsocket>> trainingWebsocketMap = connectionMap.column(trainingId);
        List<TrainingExperimentWebsocket> websocketList = new ArrayList<>();
        for (Map.Entry<Long, List<TrainingExperimentWebsocket>> trainingWebsocketEntry : trainingWebsocketMap.entrySet()) {
            websocketList.addAll(trainingWebsocketEntry.getValue());
        }

        if (CollectionUtils.isNotEmpty(websocketList)) {
            FlavorUpdateMessage flavorUpdateMessage = FlavorUpdateMessage.builder().build();
            String message = JSONObject.toJSONString(flavorUpdateMessage);
            for (TrainingExperimentWebsocket websocket : websocketList) {
                try {
                    websocket.session.getBasicRemote().sendText(message);
                } catch (IOException e) {
                    log.warn("websocket send message exception", e);
                }
            }
        }
    }

    public static Boolean isOnline(Long userId, Long trainingId) {
        return connectionMap.contains(userId, trainingId);
    }

}
